<?php

class Light_Core
{
    const EVENT_AFTER_LOAD_MODULE   = 'eventAfterLoadModule';
    const EVENT_BEFORE_ROUTE        = 'eventBeforeRoute';
    const EVENT_AFTER_ROUTE         = 'eventAfterRoute';
    const EVENT_BEFORE_DISPATCH     = 'eventBeforeDispatch';
    const EVENT_AFTER_DISPATCH      = 'eventAfterDispatch';
    const EVENT_BEFORE_EXECUTE      = 'eventBeforeExecute';
    const EVENT_AFTER_EXECUTE       = 'eventAfterExecute';
    const EVENT_BEFORE_INIT         = 'eventBeforeInit';
    const EVENT_AFTER_INIT          = 'eventAfterInit';
    const EVENT_BEFORE_ACTION       = 'eventBeforeAction';
    const EVENT_AFTER_ACTION        = 'eventAfterAction';
    const EVENT_BEFORE_DISPLAY      = 'eventBeforeDisplay';
    const EVENT_AFTER_DISPLAY       = 'eventAfterDisplay';

	/**
	 * @var array
	 */
	public static $controllers = array();

	/**
	 * @var array
	 */
	public static $modules;

	/**
	 * @var array
	 */
	public static $config;

	/**
	 * @var array
	 */
	public static $translations;

	/**
	 * @var Light_Cache_Core
	 */
	public static $cache;

	/**
	 * @var Light_Db_Adapter_Abstract
	 */
	public static $db;

	/**
	 * @var boolean
	 */
	public static $isAdmin;

	/**
	 * @var Light_Controller
	 */
	public static $mainController;

	/**
	 * @var Light_Router_Rewrite
	 */
	public static $router;

	/**
	 * @var Light_Request
	 */
	public static $request;

	/**
	 * @var string
	 */
	public static $baseUrl;

	public static $defaultConfig = array(
	    'module'     => 'Default',
	    'controller' => 'Index',
	    'action'     => 'Index'
	);

	/**
	 * @param array $params
	 */
	public static function start()
	{
	    self::loadConfig();

	    self::loadRouter();

	    self::loadModules();

	    if (!self::$router instanceof Light_Router_Rewrite) {
	        throw new Light_Exception('There is no router');
	    }

	    self::getEventManager()->trigger(self::EVENT_BEFORE_ROUTE);

	    $params = self::$router->route(trim(str_replace(self::$baseUrl, '', $_SERVER['REQUEST_URI']), '/') . '/');

	    $module     = isset($params['module']) ? $params['module'] : self::$defaultConfig['module'];
	    $controller = isset($params['controller']) ? $params['controller'] : self::$defaultConfig['controller'];
	    $action     = isset($params['action']) ? $params['action'] : self::$defaultConfig['action'];

	    if (self::$router->getCurrentRouteName() == 'admin') {
	        self::$isAdmin = true;
	    } else {
	        self::$isAdmin = false;
	    }

	    self::getEventManager()->trigger(self::EVENT_AFTER_ROUTE, $params);

		$controller = self::dispatch($module, $controller, $action, $params);

		self::execute($controller);

		self::display($controller);
	}

	/**
	 * @param string $name
	 * @return string
	 */
	public static function normalizeName($name)
	{
	    $name = preg_replace('/[^a-z0-9_.-]/ius', '', $name);

	    if (strpos($name, '.') !== false) {
	        $name = str_replace(' ', '', ucwords(str_replace('.', ' ', $name)));
	    }

	    if (strpos($name, '_') !== false) {
	        $name = str_replace(' ', '', ucwords(str_replace('_', ' ', $name)));
	    }

	    if (strpos($name, '-') !== false) {
	        $name = str_replace(' ', '', ucwords(str_replace('-', ' ', $name)));
	    }

	    return ucfirst($name);
	}

	/**
	 * @param string $module
	 * @param string $controller
	 * @param string $action
	 * @param array $params
	 * @param boolean $execute
	 * @throws Light_Exception
	 * @return Light_Controller
	 */
	public static function dispatch($module = null, $controller = null, $action = null, array $params = array())
	{
	    self::getEventManager()->trigger(self::EVENT_BEFORE_DISPATCH);

	    if (!in_array($module, self::$config['modules'])) {
	        throw new Light_Exception("Module '{$module}' is not loaded");
	    }

	    $module     = self::normalizeName($module);
	    $controller = self::normalizeName($controller);
	    $action     = self::normalizeName($action);

	    $controllerClass = $module . '_Controller_'  . (self::$isAdmin ? 'Admin_' : '') . '' . $controller;

		if (!isset(self::$controllers[$controllerClass])) {

			$obj = new $controllerClass();

			if (!$obj instanceof Light_Controller) {
				throw new Light_Exception('<b>' . $controllerClass . '</b> does not extends Light_Controller', 501);
			}

			$router = (self::$router !== null ? self::$router : self::loadRouter());

			self::$request = (self::$mainController !== null ? self::$mainController->getRequest() : new Light_Request());

			self::$request->setParam('module', strtolower($module));
			self::$request->setParam('controller', strtolower($controller));
			self::$request->setParam('action', strtolower($action));

			self::$request->setParams($params);

			$obj->setRequest(self::$request)
			    ->setRouter($router)
			    ->setModuleName(strtolower($module))
			    ->setControllerName(strtolower($controller))
			    ->setActionName(strtolower($action))
			    ->setView(new Light_View($obj));

			if (self::$mainController) {
			    $layout = self::$mainController->getLayout();
			} else {
			    $layout = new Light_Layout($obj->getView());
			}

			$obj->setLayout($layout);

			if (LIGHT_LANGUAGE_ENABLE && !self::$mainController) {
			    $lang = $obj->getRequest()->getParam('lang', LIGHT_LANGUAGE_DEFAULT);
			    self::$router->setGlobalParam('lang', $lang);
			    $obj->getRequest()->setParam('lang', $lang);
			}

    		if (self::$mainController === null) {
    		    self::$mainController = $obj;
    		}

			self::$controllers[$controllerClass] = $obj;
		}

		self::getEventManager()->trigger(self::EVENT_AFTER_DISPATCH);

		return self::$controllers[$controllerClass];
	}

	/**
	 * @param Light_Controller $obj
	 * @throws Light_Exception
	 */
	public static function execute(Light_Controller $controller = null)
	{
	    self::getEventManager()->trigger(self::EVENT_BEFORE_EXECUTE, $controller);

	    self::getEventManager()->trigger(self::EVENT_BEFORE_INIT, $controller);

	    $controller->init();

	    self::getEventManager()->trigger(self::EVENT_AFTER_INIT, $controller);

	    $action = $controller->getActionName() . 'Action';

	    if (!method_exists($controller, $action)) {
	        throw new Light_Exception('Method: <b>' . $action . '</b> not found in class: <b>' . get_class($controller) . '</b>', 404);
	    }

	    if (in_array($controller->getActionName(), $controller->getBannedActions())) {
	        throw new Light_Exception('Method: <b>' . $action . '</b> in class: <b>' . get_class($controller) . '</b> is banned', 404);
	    }

	    self::getEventManager()->trigger(self::EVENT_BEFORE_ACTION, $controller);

	    $controller->$action();

	    self::getEventManager()->trigger(self::EVENT_AFTER_ACTION, $controller);

	    self::getEventManager()->trigger(self::EVENT_AFTER_EXECUTE, $controller);
	}

	/**
	 * @param Light_Controller $controller
	 */
	public static function display(Light_Controller $controller)
	{
	    self::getEventManager()->trigger(self::EVENT_BEFORE_DISPLAY, $controller);

	    ob_start();

	    $controller->display();

	    echo ob_get_clean();

	    self::getEventManager()->trigger(self::EVENT_AFTER_DISPLAY, $controller);
	}

	/**
	 * @return array
	 */
	public static function loadConfig()
	{
	    if (self::$config !== null) {
	        return self::$config;
	    }

        self::$config = array();

	    $configFiles = glob(LIGHT_APPLICATION_PATH . '/configs/{global,local}.php', GLOB_BRACE);

	    if (is_array($configFiles) && !empty($configFiles)) {
	        foreach ($configFiles as $configFile) {
	            self::$config = Light_Util::merge(self::$config, require $configFile);
	        }
	    }

	    return self::$config;
	}

	/**
	 * @throws Light_Exception
	 * @return array
	 */
	public static function loadModules()
	{
	    if (self::$modules !== null) {
	        return self::$modules;
	    }

	    self::$modules = array();

	    $modules = glob(LIGHT_APPLICATION_PATH . '/modules/{' . implode(',', self::$config['modules']) . '}/Module.php', GLOB_BRACE);

	    foreach ($modules as $module) {

	        require_once $module;

	        $parts  = explode('/', $module);
	        $module = $parts[count($parts) - 2];
	        $class = ucfirst($module) . '_Module';
	        $class = new $class();

	        if (!$class instanceof Light_Module) {
	            throw new Light_Exception("'{$class}' must extends Light_Module");
	        }

	        $methods = get_class_methods($class);

	        foreach ($methods as $method) {
	            $class->$method();
	        }

	        self::$modules[$module] = $class;

	        self::getEventManager()->trigger(self::EVENT_AFTER_LOAD_MODULE);
	    }

	    return self::$modules;
	}

	/**
	 * @return array
	 */
	public static function loadTranslations()
	{
	    if (self::$translations !== null) {
	        return self::$translations;
	    }

	    self::$translations = array();

	    $languages = glob(LIGHT_LANGUAGE_PATH . '/*');

	    if (!empty($languages)) {
	        asort($languages);
    	    foreach ($languages as $languageFile) {
    	        $parts   = explode('/', $languageFile);
    	        $lang    = end($parts);
    	        if (!isset(self::$translations[$lang])) {
    	            self::$translations[$lang] = array();
    	        }
    	        $files = glob($languageFile . '/*');
    	        if (!empty($files)) {
    	            asort($files);
        	        foreach ($files as $file) {
            	        $translations = require $file;
            	        self::$translations[$lang] = array_merge(self::$translations[$lang], $translations);
        	        }
    	        }
    	    }
	    }

	    return self::$translations;
	}

	/**
	 * @return array
	 */
	public static function getTranslations()
	{
        return self::loadTranslations();
	}

	/**
	 * @return Light_Router_Rewrite
	 */
	public static function loadRouter()
	{
	    if (self::$router === null) {
	        self::$router = new Light_Router_Rewrite();
	        $defaultRoutes = array(
                'home'    => array('Light_Router_Route_Static', array('/', array('module' => 'default', 'controller' => 'index', 'action' => 'redirector'))),
                'default' => array('Light_Router_Route', array(':module/:controller/:action/*', array('module' => 'default', 'controller' => 'index', 'action' => 'index'))),
                'admin'   => array('Light_Router_Route', array('admin/:module/:controller/:action/*', array('module' => 'default', 'controller' => 'index', 'action' => 'index')))
	        );
	        self::addRoutes($defaultRoutes);
	    }

	    return self::$router;
	}

	/**
	 * @param array $routes
	 */
	public static function addRoutes(array $routes = array())
	{
	    self::loadRouter();

	    if (LIGHT_LANGUAGE_ENABLE) {
	        $lang = new Light_Router_Route(':lang', array('module' => 'default', 'controller' => 'index', 'action' => 'index'), array('lang' => '[a-z]{2}'));
	    }

	    foreach ($routes as $name => $config) {
	        $routeName = $config[0];
	        $route = new $routeName(
                $config[1][0],
                (isset($config[1][1]) ? $config[1][1] : array()),
                (isset($config[1][2]) ? $config[1][2] : array()),
                (isset($config[1][3]) ? $config[1][3] : array())
	        );
	        if (LIGHT_LANGUAGE_ENABLE && $name != 'home') {
	            $chain = new Light_Router_Route_Chain();
	            $chain->chain($lang)->chain($route);
	            self::$router->addRoute($name, $chain);
	        } else {
	            self::$router->addRoute($name, $route);
	        }
	    }
	}

	/**
	 * @return Light_Event_Manager
	 */
	public static function getEventManager()
	{
	    return Light_Event_Manager::getInstance();
	}

	/**
	 * @return Light_Service_Manager
	 */
	public static function getServiceManager()
	{
	    return Light_Service_Manager::getInstance();
	}
}